package com.bestv.search.engine.compass.spellcheck;

import java.io.*;
import java.util.AbstractMap.SimpleEntry;
import java.util.*;
import java.util.Map.Entry;

public class ChineseUtil {

    private final static String allChineseFile = "chinese.txt";
    private final static String chineseSpellFile = "similarSpell.txt";
    public static Set<String> unKnownChineseSet = new HashSet<String>();
    private static List<Entry<String, Integer>> spellAndAsciiList = new ArrayList<Entry<String, Integer>>();
    private static List<Entry<String, String>> similarSpellList = new ArrayList<Entry<String, String>>();
    static {
        try {
            initChineseASCII();
            initSimilarSpell();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private ChineseUtil() {
    }

    public static String[] getFullSimilarSpell(String cnStr) {
        if (null == cnStr || "".equals(cnStr.trim())) {
            return null;
        }

        // Gets full similar Chinese spell
        List<String[]> spellsList = new ArrayList<String[]>();
        char[] chars = cnStr.toCharArray();
        for (int i = 0, Len = chars.length; i < Len; i++) {
            spellsList.add(getSimilarSpellByCnChar(chars[i]));
        }

        // Combine spell between Chinese char
        ArrayList<StringBuilder> results = new ArrayList<StringBuilder>();
        for (String[] spells : spellsList) {
            int currSize = results.size();
            int spellsSize = spells.length;
            if (currSize > 0 && spellsSize > 0) {
                for (int i = spellsSize - 1; i >= 0; i--) {
                    for (int j = 0; j < currSize; j++) {
                        if (i == 0) {
                            results.get(j).append(spells[i]);
                        } else {
                            StringBuilder newSb = new StringBuilder(results.get(j).toString());
                            newSb.append(spells[i]);
                            results.add(newSb);
                        }
                    }
                }
            } else if (currSize == 0) {
                for (int i = 0; i < spellsSize; i++) {
                    StringBuilder newSb = new StringBuilder(spells[i]);
                    results.add(newSb);
                }
            }
        }

        String[] list = new String[results.size()];
        for (int i = 0; i < list.length; i++) {
            list[i] = results.get(i).toString();
        }
        return list;
    }

    /**
     * Gets full spell
     *
     * @param cnStr Chinese String
     * @return Full spell of Chinese string
     */
    public static String[] getFullSpell(String cnStr) {
        if (null == cnStr || "".equals(cnStr.trim())) {
            return null;
        }

        // Gets full Chinese spell
        List<String[]> spellsList = new ArrayList<String[]>();
        char[] chars = cnStr.toCharArray();
        for (int i = 0, Len = chars.length; i < Len; i++) {
            int ascii = getCnAscii(chars[i]);
            // Error happened when get ASCII
            if (ascii == 0) {
                spellsList.add(new String[]{String.valueOf(chars[i])});
            } else {
                String[] spells = getSpellByAscii(ascii);
                if (spells == null || spells.length == 0) {
                    spellsList.add(new String[]{String.valueOf(chars[i])});
                    if (ascii < 0 || ascii > 127) {
                        unKnownChineseSet.add(String.valueOf(chars[i]));
                    }
                } else {
                    spellsList.add(spells);
                }
            }
        }

        // Combine spell between Chinese char
        ArrayList<StringBuilder> results = new ArrayList<StringBuilder>();
        for (String[] spells : spellsList) {
            int currSize = results.size();
            int spellsSize = spells.length;
            if (currSize > 0 && spellsSize > 0) {
                for (int i = spellsSize - 1; i >= 0; i--) {
                    for (int j = 0; j < currSize; j++) {
                        if (i == 0) {
                            results.get(j).append(spells[i]);
                        } else {
                            StringBuilder newSb = new StringBuilder(results.get(j).toString());
                            newSb.append(spells[i]);
                            results.add(newSb);
                        }
                    }
                }
            } else if (currSize == 0) {
                for (int i = 0; i < spellsSize; i++) {
                    StringBuilder newSb = new StringBuilder(spells[i]);
                    results.add(newSb);
                }
            }
        }

        String[] list = new String[results.size()];
        for (int i = 0; i < list.length; i++) {
            list[i] = results.get(i).toString();
        }
        return list;
    }

    public static boolean containsChinese(String text) {
        char[] textArray = text.toCharArray();
        for (char c : textArray) {
            if (isGB18030(c)) {
                return true;
            }
        }
        return false;
    }

    private static void initChineseASCII() throws IOException {
        BufferedReader br = null;
        try {
            String folder = ChineseUtil.class.getClassLoader().getResource(File.separator).getPath();
            br = new BufferedReader(new FileReader(folder + allChineseFile));
            String line = null;
            while ((line = br.readLine()) != null) {
                String[] spellAndChinese = line.split("=");
                // If spellAndChinese.length!=2, ignore this row
                if (spellAndChinese != null && spellAndChinese.length == 2) {
                    String spell = spellAndChinese[0];
                    String currChineseStr = spellAndChinese[1];
                    char[] chineseChars = currChineseStr.toCharArray();
                    for (char c : chineseChars) {
                        int ascii = getCnAscii(c);
                        // ignore invalid Chinese char
                        if (ascii != 0) {
                            spellAndAsciiList.add(new SimpleEntry<String, Integer>(spell, Integer.valueOf(ascii)));
                        }
                    }
                }
            }
        } finally {
            if (br != null)
                br.close();
        }

        // Sort for spellAndAscii ASC by ASCII
        Collections.sort(spellAndAsciiList, new Comparator<Entry<String, Integer>>() {
            public int compare(Entry<String, Integer> o1, Entry<String, Integer> o2) {
                return o1.getValue() - o2.getValue();
            }
        });
    }

    private static void initSimilarSpell() throws IOException {
        BufferedReader br = null;
        try {
            String folder = ChineseUtil.class.getClassLoader().getResource(File.separator).getPath();
            br = new BufferedReader(new FileReader(folder + chineseSpellFile));
            String line = null;
            while ((line = br.readLine()) != null) {
                String[] similarSpellArray = line.split("=");
                if (similarSpellArray.length == 2 && similarSpellArray[1] != null && !"".equals(similarSpellArray[1])) {
                    similarSpellList.add(new SimpleEntry<String, String>(similarSpellArray[0], similarSpellArray[1]));
                }
            }
        } finally {
            if (br != null)
                br.close();
        }
    }

    /**
     * Gets ASCII of Chinese char
     *
     * @param cn char of Chinese
     * @return If error happened return 0, else return ASCII
     */
    private static int getCnAscii(char cn) {
        byte[] bytes = null;
        try {
            bytes = (String.valueOf(cn)).getBytes("GB18030");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        if (bytes == null || bytes.length > 2 || bytes.length <= 0) {
            return 0;
        }

        // English of number char
        if (bytes.length == 1) {
            return bytes[0];
        }

        // Chinese char
        if (bytes.length == 2) {
            int hightByte = 256 + bytes[0];
            int lowByte = 256 + bytes[1];

            int ascii = (256 * hightByte + lowByte) - 256 * 256;
            return ascii;
        }

        // Error
        return 0;
    }

    /**
     * Gets spell base on ASCII
     *
     * @param ascii ASCII
     * @return full spell of chinese char
     */
    private static String[] getSpellByAscii(int ascii) {
        List<String> results = new ArrayList<String>();

        int low = 0;
        int high = spellAndAsciiList.size() - 1;
        int middle;
        while (low <= high) {
            middle = (low + high) / 2;
            Entry<String, Integer> entry = spellAndAsciiList.get(middle);
            if (entry.getValue() == ascii) {
                results.add(entry.getKey());

                // handle multilple speaking Chinese
                boolean continueFlag = true;
                int loopIndex = middle;
                do {
                    loopIndex--;
                    if (loopIndex < 0) {
                        break;
                    }
                    entry = spellAndAsciiList.get(loopIndex);
                    if (entry.getValue() == ascii) {
                        results.add(entry.getKey());

                    } else {
                        continueFlag = false;
                    }
                } while (continueFlag);

                continueFlag = true;
                loopIndex = middle;
                do {
                    loopIndex++;
                    if (loopIndex > spellAndAsciiList.size() - 1) {
                        break;
                    }
                    entry = spellAndAsciiList.get(loopIndex);
                    if (entry.getValue() == ascii) {
                        results.add(entry.getKey());

                    } else {
                        continueFlag = false;
                    }
                } while (continueFlag);

                // founded, break;
                break;
            }
            if (ascii < entry.getValue()) {
                high = middle - 1;
            }
            if (ascii > entry.getValue()) {
                low = middle + 1;
            }
        }
        return results.toArray(new String[results.size()]);
    }

    private static String[] getSimilarSpellByCnChar(char cn) {
        if (isGB18030(cn)) {
            List<String[]> list = new ArrayList<String[]>();
            int total = 0;
            int ascii = getCnAscii(cn);
            String[] spells = getSpellByAscii(ascii);
            if (spells != null && spells.length > 0) {
                // Add original spells to list
                list.add(spells);
                total = spells.length;

                // add similar spell to list
                for (String spell : spells) {
                    String[] thisSpells = getSimilarSpell(spell);
                    if (thisSpells != null && thisSpells.length > 0) {
                        list.add(thisSpells);
                        total += thisSpells.length;
                    }
                }

                // Convert list to array.
                String[] result = new String[total];
                int index = 0;
                for (String[] array : list) {
                    for (String spell : array) {
                        result[index++] = spell;
                    }
                }

                // return result
                return result;
            } else {
                return new String[]{String.valueOf(cn)};
            }
        } else {
            return new String[]{String.valueOf(cn)};
        }
    }

    private static String[] getSimilarSpell(String spell) {
        for (Entry<String, String> entry : similarSpellList) {
            if (entry.getKey().equals(spell)) {
                String value = entry.getValue();
                if (value != null) {
                    return entry.getValue().split(",");
                }
            }
        }
        return new String[0];
    }

    // Check whether is GB18030 chinese char
    private static boolean isGB18030(char cn) {
        byte[] bytes = null;
        try {
            bytes = (String.valueOf(cn)).getBytes("GB18030");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        if (bytes == null || bytes.length > 2 || bytes.length <= 0) {
            return false;
        }

        // English of number char
        if (bytes.length == 1) {
            return false;
        }

        int iHead = bytes[0] & 0xff;
        int iTail = bytes[1] & 0xff;
        return ((iHead >= 0x81 && iHead <= 0xFE && ((iTail >= 0x40 && iTail <= 0x7E) || (iTail >= 0x80 && iTail <= 0xFE))) ? true
                : false);
    }
}
